﻿using System;
using System.Runtime.InteropServices;
using OpenGL.Enums;
using OpenGL.Exceptions;
using OpenGL.Types;

namespace OpenGL
{
    internal delegate Shader glCreateShaderDelegate(ShaderType type);
    internal delegate void glShaderSourceDelegate(Shader shader, uint count, string[] codes, int[] lengths);
    internal delegate void glCompileShaderDelegate(Shader shader);
    internal delegate void glGetShaderivDelegate(Shader shader, ShaderStatus status, out int result);
    internal delegate ShaderProgram glCreateProgramDelegate();
    internal delegate void glAttachShaderDelegate(ShaderProgram program, Shader shader);
    internal delegate void glLinkProgramDelegate(ShaderProgram program);
    internal delegate void glBindAttribLocationDelegate(ShaderProgram program, int index, string name);
    internal delegate void glUseProgramDelegate(ShaderProgram program);
    internal delegate void glGetProgramivDelegate(ShaderProgram program, ShaderStatus status, out int result);

    static public partial class GL
    {
        private static glCreateShaderDelegate _createShader = null;
        private static glShaderSourceDelegate _shaderSource = null;
        private static glCompileShaderDelegate _compileShader = null;
        private static glGetShaderivDelegate _getShaderiv = null;
        private static glCreateProgramDelegate _createProgram = null;
        private static glAttachShaderDelegate _attachShader = null;
        private static glLinkProgramDelegate _linkProgram = null;       
        private static glBindAttribLocationDelegate _bindAttribLocation = null;
        private static glUseProgramDelegate _useProgram = null;
        private static glGetProgramivDelegate _getProgramiv = null;

        internal static Shader CreateShader(ShaderType shaderType)
        {
            return _createShader(shaderType);
        }
        internal static void ShaderSource(Shader shader, string code)
        {
            _shaderSource(shader, 1, new string[] { code }, new int[1] { code.Length });
        }
        internal static void CompileShader(Shader shader)
        {
            _compileShader(shader);
        }
        internal static void GetShaderiv(Shader shader, ShaderStatus status, out int result)
        {
            _getShaderiv(shader, status, out result);
        }
        internal static ShaderProgram CreateProgram()
        {
            return _createProgram();
        }
        internal static void AttachShader(ShaderProgram program, Shader shader)
        {
            _attachShader(program, shader);
        }
        internal static void LinkProgram(ShaderProgram program)
        {
            _linkProgram(program);
        }
        internal static void BindAttribLocation(ShaderProgram program, int index, string name)
        {
            _bindAttribLocation(program, index, name);
        }
        internal static void UseProgram(ShaderProgram program)
        {
            _useProgram(program);
        }
        internal static void GetProgramiv(ShaderProgram program, ShaderStatus status, out int result)
        {
            _getProgramiv(program, status, out result);
        }

        public static void InitExtensions()
        {
            _createShader = GetProcDelegate<glCreateShaderDelegate>("glCreateShader");
            _shaderSource = GetProcDelegate<glShaderSourceDelegate>("glShaderSource");
            _compileShader = GetProcDelegate<glCompileShaderDelegate>("glCompileShader");
            _getShaderiv = GetProcDelegate<glGetShaderivDelegate>("glGetShaderiv");
            _createProgram = GetProcDelegate<glCreateProgramDelegate>("glCreateProgram");
            _attachShader = GetProcDelegate<glAttachShaderDelegate>("glAttachShader");
            _linkProgram = GetProcDelegate<glLinkProgramDelegate>("glLinkProgram");
            _bindAttribLocation = GetProcDelegate<glBindAttribLocationDelegate>("glBindAttribLocation");
            _useProgram = GetProcDelegate<glUseProgramDelegate>("glUseProgram");
            _getProgramiv = GetProcDelegate<glGetProgramivDelegate>("glGetProgramiv");
        }

        private static T GetProcDelegate<T>(string procName)
        {
            try
            {
                IntPtr proc = wglGetProcAddress(procName);
                var del = Marshal.GetDelegateForFunctionPointer(proc, typeof(T));
                return (T)(object)del;
            }
            catch
            {
                throw new OpenGLLoadExtensionException(procName);
            }
        }
    }
}